import re
import time
import pickle
from random import choice
from urllib.parse import urlsplit

from redis import StrictRedis

from core.utils import process_obj
from core.connection import connection_pool
from settings import SOURCE_PROXY_KEY, SOURCE_LIST_KEY


class RedisServer(object):

    def __init__(self):
        self.redis = StrictRedis(connection_pool=connection_pool)

    def handle_key(self, key):
        # 将key头部或尾部的 '' 或 "" 去掉
        if key.startswith("'") or key.startswith('"'):
            key = key.lstrip("'").lstrip('"')
        if key.endswith("'") or key.endswith('"'):
            key = key.rstrip("'").rstrip('"')
        return key

    def get_proxy(self, key):
        '''
        获取某个源的一个代理
        '''
        if len(key) == 0:
            return self.get_one_proxy_from_interface()
        key = self.handle_key(key)
        if self._is_host(key):
            return self._get_source_proxy(key)
        if self._is_url(key):
            key = self._get_netloc(key)
            return self._get_source_proxy(key)
        return 'ERROR: URL格式有误'

    def get_source_list_name(self):
        '''
        获取Redis中，所有源的队列名称
        '''
        name_list = [name.decode().split(':')[1] for name in self.redis.keys('*') if name.decode().startswith('proxy_pool')]
        return name_list

    def get_source_list(self):
        '''
        获取所有的源
        '''
        source_list = self.redis.lrange(SOURCE_LIST_KEY, 0, -1)
        return [pickle.loads(source) for source in source_list]

    def get_all_proxy_from_source(self, key, withscores=False):
        '''
        返回某个源的所有代理
        '''
        key = self.handle_key(key)
        if not self._is_host(key):
            if self._is_url(key):
                key = self._get_netloc(key)
            else:
                return '【ERROR】 URL：{} 格式有误'.format(key)
        info_list = self.redis.zrange(SOURCE_PROXY_KEY % key, 0, -1, withscores=withscores)
        if withscores is True:
            result = []
            for info in info_list:
                pickle_dict = pickle.loads(info[0])
                if pickle_dict.get('expiration', None) is not None:
                    timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(pickle_dict['expiration']))
                    pickle_dict['expiration'] = timestamp
                pickle_dict['use_times'] = info[1]
                result.append(pickle_dict)
            return result
        return [pickle.loads(info) for info in info_list]

    def push_source_proxy(self, key, proxy_info):
        '''
        向指定源的队列中放入存放代理(字典)  {'proxy': '127.0.0.1', 'expiration': '时间戳'}
        '''
        all_proxy = self.get_all_proxy_from_source(key)
        # 存在一种情况，两个相同的代理过期时间相差一秒，所以在压入之前再进行一次判断
        # 如果相差一秒，那么pick.dumps()后的结果不同，那么Redis中就存在了相同的代理
        if proxy_info['proxy'] in [proxy['proxy'] for proxy in all_proxy]:
            return
        self.redis.zadd(SOURCE_PROXY_KEY % key, {pickle.dumps(proxy_info): 0})

    def delete_one_proxy_from_source(self, key, proxy_info):
        '''
        删除某个源下的指定代理
        '''
        self.redis.zrem(SOURCE_PROXY_KEY % key, pickle.dumps(proxy_info))

    def get_one_proxy_from_interface(self):
        '''
        从底层池中随机取出一个代理
        '''
        return choice(self.get_all_proxy_from_interface())['proxy']

    def get_all_proxy_from_interface(self):
        '''
        获取底层池中的所有代理
        '''
        return process_obj.process()

    def delete_source(self, key):
        '''
        删除某个源
        '''
        self.redis.lrem(SOURCE_LIST_KEY, 1, pickle.dumps(key))

    def delete_source_list_from_key(self, key):
        '''
        删除某个源的队列
        '''
        self.redis.delete(SOURCE_PROXY_KEY % key)

    def _is_url(self, url):
        '''
        判断是否是一个URL
        '''
        url_regex = r'(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]'
        return re.match(url_regex, url)

    def _is_host(self, url):
        '''
        判断url是否是以'www'开头
        '''
        if url.startswith('www'):
            return url
        return False

    def _get_netloc(self, url):
        '''
        获取URL中的域名
        '''
        netloc = urlsplit(url).netloc
        netloc = re.sub(r'\w+', 'www', netloc, 1)
        return netloc

    def _push_source(self, host):
        '''
        向源队列中放入一个源
        '''
        # 如果某个源的所有代理在某个时间全部过期，这时获取这个源的代理为空，
        # 就会向源队列中添加这个源，这时就需要判断，源队列中是否存在这个源，
        # 不存在则添加这个源，存在则不添加，保证源队列中没有重复的源

        # 使用队列的原因是为了可以设置优先级，后来的域名放在队列的最左边，这样
        # 检测的时候，后来的域名最先被检测
        if host in self.get_source_list():
            return
        self.redis.lpush(SOURCE_LIST_KEY, pickle.dumps(host))

    def _get_source_proxy(self, key):
        '''
        返回指定源中使用次数最少的一个代理
        '''
        # info_list   [({'proxy': '127.0.0.1', 'expiration': '时间戳', 'use_times': 0}), ({}, 1)]
        info_list = self.get_all_proxy_from_source(key, withscores=True)
        if len(info_list) == 0:
            # 从底层代理池中取出代理返回
            proxy = self.get_one_proxy_from_interface()
            self._push_source(key)
            return proxy
        score_list = [int(info['use_times']) for info in info_list]
        min_score = min(score_list)
        # 如果 min == max 选择min    如果 min < max 选择min
        return self._get_one_proxy_from_score(key, min_score)

    def _get_one_proxy_from_score(self, key, score):
        '''
        返回指定源中指定分数范围内的一个代理
        '''
        proxy_info = pickle.loads(choice(self.redis.zrangebyscore(SOURCE_PROXY_KEY % key, score, score)))
        self._inc_source(key, proxy_info)
        return proxy_info['proxy']

    def _inc_source(self, key, proxy_info):
        '''
        将某个源中某条代理分数(使用次数)加1
        '''
        self.redis.zincrby(SOURCE_PROXY_KEY % key, 1, pickle.dumps(proxy_info))
